﻿using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

using WpfAdmin.Everything.Enums;

namespace WpfAdmin.Everything {
    internal class EverythingWrapper {
        private static readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
        private class Locker : IDisposable {
            private readonly ReaderWriterLockSlim locker;
            public Locker(ReaderWriterLockSlim locker) {
                this.locker = locker;
                this.locker.EnterWriteLock();
            }
            public void Dispose() {
                this.locker.ExitWriteLock();
            }
        }
        internal static IDisposable Lock() {
            return new Locker(locker);
        }

        internal const string PROCESS_NAME = "Everything";
        internal const string EXE_NAME = "Everything.exe";
        internal const string LIB_NAME = "Everything.dll";
        internal const string ARCH_X64 = "x64";
        internal const string ARCH_X86 = "x86";

        private static string _workPath;
        public static void PrepareEverything(string workPath = null) {
            _workPath = workPath;
            if (string.IsNullOrEmpty(_workPath)) {
                _workPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Everything");
            }

            ExtractEmbededResource($"Everything/{EXE_NAME}", _workPath);
            ExtractEmbededResource($"Everything/{ARCH_X64}/{LIB_NAME}", $"{_workPath}/{ARCH_X64}");
            ExtractEmbededResource($"Everything/{ARCH_X86}/{LIB_NAME}", $"{_workPath}/{ARCH_X86}");

            bool is64BitProcess = IntPtr.Size == 8;
            string archFolder = is64BitProcess ? ARCH_X64 : ARCH_X86;
            string dllFile = Path.Combine(_workPath, archFolder, LIB_NAME);
            if (!File.Exists(dllFile)) {
                throw new FileNotFoundException($"未找到 {LIB_NAME}", dllFile);
            }
            string exePath = Path.Combine(_workPath, EXE_NAME);
            if (!File.Exists(exePath)) {
                throw new FileNotFoundException($"未找到 {EXE_NAME}", exePath);
            }
            IntPtr handle = LoadLibraryA(dllFile);
            if (handle == IntPtr.Zero) {
                throw new InvalidOperationException($"加载 {LIB_NAME} 失败：{dllFile}");
            }
        }

        internal static void StartProcess(string option) {
            try {
                var exePath = Path.Combine(_workPath, EXE_NAME);
                Process process = new Process();
                process.StartInfo.FileName = exePath;
                process.StartInfo.Arguments = option;
                process.StartInfo.UseShellExecute = true;
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                // 是否以管理员方式启动由上层 EverythingState.Init(admin) 控制：
                // 仅当显式包含 -admin 参数时才请求 UAC 提权
                if (!string.IsNullOrWhiteSpace(option) && option.Contains("-admin")) {
                    process.StartInfo.Verb = "runas";
                }
                process.Start();

                bool flag = false;
                int marjor = 0;
                int tryCnt = 0;
                while (!flag && tryCnt < 120 && marjor <= 0) {
                    try {
                        tryCnt++;
                        flag = process.WaitForInputIdle(500);
                        marjor = (int)Everything_GetMajorVersion();
                    } catch (Exception ee) {
                        Console.WriteLine(ee.Message);
                    }
                }
                Console.WriteLine($">>>>>> flag={flag}, tryCnt={tryCnt}, major={marjor}");
            } catch (Exception ex) {
                throw new InvalidOperationException("启动 Everything 服务进程失败", ex);
            }
        }

        /// <summary>
        /// 解压缩 Everything 依赖文件（嵌入式资源）到指定目录
        /// </summary>
        /// <param name="resourceRelativePath">嵌入式资源相对路径</param>
        /// <param name="targetDir">指定目录</param>
        private static void ExtractEmbededResource(string resourceRelativePath, string targetDir) {
            if (!Directory.Exists(targetDir)) {
                Directory.CreateDirectory(targetDir);
            }
            string filePath = Path.Combine(targetDir, Path.GetFileName(resourceRelativePath));
            if (File.Exists(filePath)) {
                return;
            }

            var resourceName = "WpfAdmin.Everything." + resourceRelativePath.Replace("/", ".");
            var assembly = Assembly.GetExecutingAssembly();
            using (Stream stream = assembly.GetManifestResourceStream(resourceName)) {
                using (FileStream fs = new FileStream(filePath, FileMode.Create)) {
                    stream.CopyTo(fs);
                }
            }
        }

        #region DllImport
        [DllImport("kernel32")]
        private static extern IntPtr LoadLibraryA([MarshalAs(UnmanagedType.LPStr)] string fileName);

        [DllImport(LIB_NAME)]
        public static extern bool Everything_IsDBLoaded();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetMajorVersion();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetMinorVersion();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetRevision();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetBuildNumber();
        [DllImport(LIB_NAME)]
        public static extern int Everything_SetSearch(string lpSearchString);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetMatchPath(bool bEnable);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetMatchCase(bool bEnable);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetMatchWholeWord(bool bEnable);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetRegex(bool bEnable);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetMax(uint dwMax);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetOffset(uint dwOffset);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetReplyWindow(IntPtr handler);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetReplyID(uint nId);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetMatchPath();
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetMatchCase();
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetMatchWholeWord();
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetRegex();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetMax();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetOffset();
        [DllImport(LIB_NAME)]
        public static extern string Everything_GetSearch();
        [DllImport(LIB_NAME)]
        public static extern int Everything_GetLastError();
        [DllImport(LIB_NAME)]
        public static extern bool Everything_Query(bool bWait);
        [DllImport(LIB_NAME)]
        public static extern void Everything_SortResultsByPath();
        [DllImport(LIB_NAME)]
        public static extern int Everything_GetNumFileResults();
        [DllImport(LIB_NAME)]
        public static extern int Everything_GetNumFolderResults();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetNumResults();
        [DllImport(LIB_NAME)]
        public static extern int Everything_GetTotFileResults();
        [DllImport(LIB_NAME)]
        public static extern int Everything_GetTotFolderResults();
        [DllImport(LIB_NAME)]
        public static extern int Everything_GetTotResults();
        [DllImport(LIB_NAME)]
        public static extern bool Everything_IsVolumeResult(uint nIndex);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_IsFolderResult(uint nIndex);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_IsFileResult(uint nIndex);
        [DllImport(LIB_NAME, CharSet = CharSet.Unicode)]
        public static extern void Everything_GetResultFullPathName(uint nIndex, StringBuilder lpString, int nMaxCount);
        [DllImport(LIB_NAME)]
        public static extern void Everything_Reset();
        [DllImport(LIB_NAME)]
        public static extern void Everything_CleanUp();

        // Everything 1.4
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetSort(uint dwSortType);
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetSort();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetResultListSort();
        [DllImport(LIB_NAME)]
        public static extern void Everything_SetRequestFlags(uint dwRequestFlags);
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetRequestFlags();
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetResultListRequestFlags();
        [DllImport(LIB_NAME, CharSet = CharSet.Unicode)]
        public static extern string Everything_GetResultExtension(uint nIndex);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetResultSize(uint nIndex, out long lpFileSize);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetResultDateCreated(uint nIndex, out long lpFileTime);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetResultDateModified(uint nIndex, out long lpFileTime);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetResultDateAccessed(uint nIndex, out long lpFileTime);
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetResultAttributes(uint nIndex);
        [DllImport(LIB_NAME, CharSet = CharSet.Unicode)]
        public static extern string Everything_GetResultFileListFileName(uint nIndex);
        [DllImport(LIB_NAME, CharSet = CharSet.Unicode)]
        public static extern string Everything_GetResultPath(uint nIndex);
        [DllImport(LIB_NAME, CharSet = CharSet.Unicode)]
        public static extern string Everything_GetResultFileName(uint nIndex);
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetResultRunCount(uint nIndex);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetResultDateRun(uint nIndex, out long lpFileTime);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_GetResultDateRecentlyChanged(uint nIndex, out long lpFileTime);
        [DllImport(LIB_NAME, CharSet = CharSet.Unicode)]
        public static extern string Everything_GetResultHighlightedFileName(uint nIndex);
        [DllImport(LIB_NAME, CharSet = CharSet.Unicode)]
        public static extern string Everything_GetResultHighlightedPath(uint nIndex);
        [DllImport(LIB_NAME, CharSet = CharSet.Unicode)]
        public static extern string Everything_GetResultHighlightedFullPathAndFileName(uint nIndex);
        [DllImport(LIB_NAME)]
        public static extern uint Everything_GetRunCountFromFileName(string lpFileName);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_SetRunCountFromFileName(string lpFileName, uint dwRunCount);
        [DllImport(LIB_NAME)]
        public static extern uint Everything_IncRunCountFromFileName(string lpFileName);
        [DllImport(LIB_NAME)]
        public static extern bool Everything_IsFileInfoIndexed(FileInfoIndex fileInfoType);
        #endregion
    }
}
